/*-------------------------------------------------------------------------
 *
 * char.c
 *	  用于内置类型“char”的函数（不要与 bpchar 混淆，后者是 SQL CHAR(n) 类型）。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/char.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <limits.h>

#include "libpq/pqformat.h"
#include "utils/builtins.h"

#define ISOCTAL(c)   (((c) >= '0') && ((c) <= '7'))
#define TOOCTAL(c)   ((c) + '0')
#define FROMOCTAL(c) ((unsigned char) (c) - '0')


/*****************************************************************************
 *	 用户 I/O 例程														 *
 *****************************************************************************/

/*
 *		charin			- 将 "x" 转换为 'x'
 *
 * 这接受 charout 生成的格式。如果我们有多字节输入
 * 不是以 '\ooo' 的形式，那么我们将其首字节视为值
 * 并默默丢弃其余部分；这是为了向后兼容。
 */
Datum charin(PG_FUNCTION_ARGS)
{
	char	   *fc_ch = PG_GETARG_CSTRING(0);

	if (strlen(fc_ch) == 4 && fc_ch[0] == '\\' &&
		ISOCTAL(fc_ch[1]) && ISOCTAL(fc_ch[2]) && ISOCTAL(fc_ch[3]))
		PG_RETURN_CHAR((FROMOCTAL(fc_ch[1]) << 6) +
					   (FROMOCTAL(fc_ch[2]) << 3) +
					   FROMOCTAL(fc_ch[3]));
	/* 这将对零长度输入字符串做正确处理 */
	PG_RETURN_CHAR(fc_ch[0]);
}

/*
 *		charout			- 将 'x' 转换为 "x"
 *
 * 可用的输出格式有：
 * 1. 0x00 表示为空字符串。
 * 2. 0x01..0x7F 表示为单个 ASCII 字节。
 * 3. 0x80..0xFF 表示为 \ooo（反斜杠和3个八进制数字）。
 * 第3种情况旨在与传统的 bytea “转义”格式相匹配。
 */
Datum charout(PG_FUNCTION_ARGS)
{
	char		fc_ch = PG_GETARG_CHAR(0);
	char	   *fc_result = (char *) palloc(5);

	if (IS_HIGHBIT_SET(fc_ch))
	{
		fc_result[0] = '\\';
		fc_result[1] = TOOCTAL(((unsigned char) fc_ch) >> 6);
		fc_result[2] = TOOCTAL((((unsigned char) fc_ch) >> 3) & 07);
		fc_result[3] = TOOCTAL(((unsigned char) fc_ch) & 07);
		fc_result[4] = '\0';
	}
	else
	{
		/* 这也对 0x00 产生可接受的结果 */
		fc_result[0] = fc_ch;
		fc_result[1] = '\0';
	}
	PG_RETURN_CSTRING(fc_result);
}

/*
 *		charrecv			- 将外部二进制格式转换为字符
 *
 * 外部表示是一个字节，没有字符集
 * 转换。这在某种程度上可能是可疑的，但在许多
 * 情况下，人们使用字符作为1字节的二进制类型。
 */
Datum charrecv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);

	PG_RETURN_CHAR(pq_getmsgbyte(fc_buf));
}

/*
 *		charsend			- 将字符转换为二进制格式
 */
Datum charsend(PG_FUNCTION_ARGS)
{
	char		fc_arg1 = PG_GETARG_CHAR(0);
	StringInfoData fc_buf;

	pq_begintypsend(&fc_buf);
	pq_sendbyte(&fc_buf, fc_arg1);
	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}

/*****************************************************************************
 *	 公共例程														 *
 *****************************************************************************/

/*
 * 注意：比较是按 char 被视为无符号 (uint8) 进行的。
 * 转换到和从整数是按 char 被视为有符号 (int8) 进行的。
 *
 * 你想要一致性吗？
 */

Datum chareq(PG_FUNCTION_ARGS)
{
	char		fc_arg1 = PG_GETARG_CHAR(0);
	char		fc_arg2 = PG_GETARG_CHAR(1);

	PG_RETURN_BOOL(fc_arg1 == fc_arg2);
}

Datum charne(PG_FUNCTION_ARGS)
{
	char		fc_arg1 = PG_GETARG_CHAR(0);
	char		fc_arg2 = PG_GETARG_CHAR(1);

	PG_RETURN_BOOL(fc_arg1 != fc_arg2);
}

Datum charlt(PG_FUNCTION_ARGS)
{
	char		fc_arg1 = PG_GETARG_CHAR(0);
	char		fc_arg2 = PG_GETARG_CHAR(1);

	PG_RETURN_BOOL((uint8) fc_arg1 < (uint8) fc_arg2);
}

Datum charle(PG_FUNCTION_ARGS)
{
	char		fc_arg1 = PG_GETARG_CHAR(0);
	char		fc_arg2 = PG_GETARG_CHAR(1);

	PG_RETURN_BOOL((uint8) fc_arg1 <= (uint8) fc_arg2);
}

Datum chargt(PG_FUNCTION_ARGS)
{
	char		fc_arg1 = PG_GETARG_CHAR(0);
	char		fc_arg2 = PG_GETARG_CHAR(1);

	PG_RETURN_BOOL((uint8) fc_arg1 > (uint8) fc_arg2);
}

Datum charge(PG_FUNCTION_ARGS)
{
	char		fc_arg1 = PG_GETARG_CHAR(0);
	char		fc_arg2 = PG_GETARG_CHAR(1);

	PG_RETURN_BOOL((uint8) fc_arg1 >= (uint8) fc_arg2);
}


Datum chartoi4(PG_FUNCTION_ARGS)
{
	char		fc_arg1 = PG_GETARG_CHAR(0);

	PG_RETURN_INT32((int32) ((int8) fc_arg1));
}

Datum i4tochar(PG_FUNCTION_ARGS)
{
	int32		fc_arg1 = PG_GETARG_INT32(0);

	if (fc_arg1 < SCHAR_MIN || fc_arg1 > SCHAR_MAX)
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("\"char\" out of range")));

	PG_RETURN_CHAR((int8) fc_arg1);
}


Datum text_char(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	char	   *fc_ch = VARDATA_ANY(fc_arg1);
	char		fc_result;

	/*
	 * 转换规则与 charin() 中的相同，但在这里我们需要
	 * 诚实地处理空字符串的情况。
	 */
	if (VARSIZE_ANY_EXHDR(fc_arg1) == 4 && fc_ch[0] == '\\' &&
		ISOCTAL(fc_ch[1]) && ISOCTAL(fc_ch[2]) && ISOCTAL(fc_ch[3]))
		fc_result = (FROMOCTAL(fc_ch[1]) << 6) +
			(FROMOCTAL(fc_ch[2]) << 3) +
			FROMOCTAL(fc_ch[3]);
	else if (VARSIZE_ANY_EXHDR(fc_arg1) > 0)
		fc_result = fc_ch[0];
	else
		fc_result = '\0';

	PG_RETURN_CHAR(fc_result);
}

Datum char_text(PG_FUNCTION_ARGS)
{
	char		fc_arg1 = PG_GETARG_CHAR(0);
	text	   *fc_result = palloc(VARHDRSZ + 4);

	/*
	 * 转换规则与 charout() 中相同，但在这里我们需要诚实地将 0x00 转换为空字符串。
	 */
	if (IS_HIGHBIT_SET(fc_arg1))
	{
		SET_VARSIZE(fc_result, VARHDRSZ + 4);
		(VARDATA(fc_result))[0] = '\\';
		(VARDATA(fc_result))[1] = TOOCTAL(((unsigned char) fc_arg1) >> 6);
		(VARDATA(fc_result))[2] = TOOCTAL((((unsigned char) fc_arg1) >> 3) & 07);
		(VARDATA(fc_result))[3] = TOOCTAL(((unsigned char) fc_arg1) & 07);
	}
	else if (fc_arg1 != '\0')
	{
		SET_VARSIZE(fc_result, VARHDRSZ + 1);
		*(VARDATA(fc_result)) = fc_arg1;
	}
	else
		SET_VARSIZE(fc_result, VARHDRSZ);

	PG_RETURN_TEXT_P(fc_result);
}
